跳到主要内容

Shell 格式化输出和解析

打印消息

echo "Hello, world!"
# or
printf "%-5s %-10s %-4s\n" No Name Mark
printf "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456
printf "%-5s %-10s %-4.2f\n" 2 James 90.9989
printf "%-5s %-10s %-4.2f\n" 3 Jeff 77.564

可以得到以下格式化输出

Hello, world!
No Name Mark
1 Sarath 80.35
2 James 91.00
3 Jeff 77.56
提示

Go 也提供了类似的工具,具体看 Go 官方包的文本对齐工具 tabwriter 那篇笔记

sort 对输入进行排序

sort 命令是在 Linux 里非常有用,它将文件进行排序,并将排序结果标准输出。sort 命令既可以从特定的文件,也可以从 stdin 中获取输入。

sort 将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按 ASCII 码值进行比较,最后将他们按升序输出。

$ cat netstat.txt      
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT

$ sort netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED

sort 默认的排序方式是升序,如果想改成降序,就加个 -r 就搞定了。

uniq 删除文件中重复的行

$ cat testfile      #原有内容  
test 30
test 30
test 30
Hello 95
Hello 95
Hello 95
Hello 95
Linux 85
Linux 85

$ uniq testfile #删除重复行后的内容
test 30
Hello 95
Linux 85

awk 命令

awk 的核心是格式化。

一切都将从一个 awk 关键字开始,然后是一个 awk 用单引号括起来的程序。最后是该 awk 命令将处理的文件的名称。

# 只执行一个动作
awk '{action}' 文件名

# 仅根据模式搜索文本
awk '模式' 文件名

# 结合模式和动作的使用
awk '模式 {action}' 文件名

以下举例,这里只介绍常用的操作

从 netstat 命令中提取了如下信息作为用例:

$ cat netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
tcp 0 0 localhost:33279 localhost:53712 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46540 TIME_WAIT
tcp 0 0 localhost:44891 localhost:45958 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46162 TIME_WAIT
tcp 0 0 localhost:38144 localhost:33053 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46518 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46444 TIME_WAIT

awk 内置的变量

其中的 $1..$n 表示第几例。注:$0 表示整个行。

$ awk '{print $1, $4}' netstat.txt          
Active (w/o
Proto Local
tcp alsritter-PC:35984
tcp localhost:44891
tcp localhost:60202
tcp localhost:44891
tcp localhost:44891
tcp localhost:33279
....

awk 的一些内置的变量:

变量名作用
$0当前记录(这个变量中存放着整个行的内容)
$1~$n当前记录的第n个字段,字段间由FS分隔
FS输入字段分隔符 默认是空格或Tab
NF当前记录中的字段个数,就是有多少列
NR已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。
FNR当前记录数,与NR不同的是,这个值会是各个文件自己的行号
RS输入的记录分隔符, 默认为换行符
OFS输出字段分隔符, 默认也是空格
ORS输出的记录分隔符,默认为换行符
FILENAME当前输入文件的名字

比如:我们如果要输出行号

$ awk '$3==0 && $6=="ESTABLISHED" || NR==1 {printf "%02s %s %-20s %-20s %s\n",NR, FNR, $4,$5,$6}' netstat.txt
1 1 (w/o servers)
3 3 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
5 5 localhost:60202 localhost:30136 ESTABLISHED
8 8 localhost:33279 localhost:53712 ESTABLISHED
12 12 localhost:38144 localhost:33053 ESTABLISHED
....

还有一些常用逻辑符号

~ 匹配,与==相比不是精确比较
!~ 不匹配,不精确比较
== 等于,必须全部相等,精确比较
!= 不等于,精确比较
&&  逻辑与
|| 逻辑或

awk 换行符

注意,默认的分隔符是空格,如果需要修改分隔符可以使用 -F,例如使用冒号当分隔符

$ awk -F: '{print $1, $4}' netstat.txt  
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 alsritter-PC
tcp 0 0 localhost
tcp 0 0 localhost
tcp 0 0 localhost
....

如果你要指定多个分隔符

$ awk -F '[;:]'

awk 格式化

再来看看 awk 的格式化输出,和C语言的 printf 没什么两样:

$ awk '{printf "%-8s %-8s %-8s %-18s %-22s %-15s\n",$1,$2,$3,$4,$5,$6}' netstat.txt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign
tcp 0 0 alsritter-PC:35984 public1.alidns.co:https ESTABLISHED
tcp 0 0 localhost:44891 localhost:46338 TIME_WAIT
tcp 0 0 localhost:60202 localhost:30136 ESTABLISHED
tcp 0 0 localhost:44891 localhost:46660 TIME_WAIT
tcp 0 0 localhost:44891 localhost:46636 TIME_WAIT
....

awk 正则匹配

$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
18 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2

$ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
15 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT
18 coolshell.cn:80 117.136.20.85:50025 FIN_WAIT2

上面的第一个示例匹配 FIN 状态, 第二个示例匹配 WAIT 字样的状态。~ 表示模式开始。/ / 中是模式。这就是一个正则表达式的匹配。

可以使用 /FIN|TIME/ 来匹配 FIN 或者 TIME :

$ awk '$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
1 Local-Address Foreign-Address State
5 coolshell.cn:80 124.205.5.146:18245 TIME_WAIT
6 coolshell.cn:80 61.140.101.185:37538 FIN_WAIT2
9 coolshell.cn:80 116.234.127.77:11502 FIN_WAIT2
11 coolshell.cn:80 183.60.215.36:36970 TIME_WAIT
13 coolshell.cn:80 124.152.181.209:26825 FIN_WAIT1
15 coolshell.cn:80 183.60.212.163:51082 TIME_WAIT

模式取反的例子:

$ awk '$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
# or
$ awk '!/WAIT/' netstat.txt

sed 命令

sed 的核心是正则

TODO: ...

References